/* **************************************************************************** */
/*                                                             __                */
/*         by: joknic                                      .´　＿｀ヽ            */
/*                                                        i　l/i￣｀ｌ|          */
/*         date: 2018/05/26 14:16:08                      | i| ﾟ ヮﾟﾉ|           */
/*                                                        | l|iﾞ､!i/l.l|         */
/*         email: 860001401@qq.com                        | ,liU/|l〉,|          */
/*                                                            し'ﾉ               */
/*                                                                               */
/* ****************************************************************************  */
(function () {
  const chart = function (options) {
    this.options = $.extend({
      id: "",
      spacing: 15,
      nodes: [],
      edges: [],
      color: {
        node: {
          Person: "#f75000",
          Company: "#4682b4"
        },
        opacityNode: {
          Person: "#FFCBB3",
          Company: "#dcecfc"
        },
        line: {
          INVEST: "#fd485e",
          EMPLOY: "#4ea2f0",
          LEGAL: "#4ea2f0"
        },
        stroke: {
          Person: "#FF8247",
          Company: "#6495ED"
        },
        opacityStroke: {
          Person: "#FFE7BA",
          Company: "#B2DFEE"
        },
        main: {
          node: "#EE3B3B",
          stroke: "#EE6363",
          opacityNode: "#FFC1C1",
          opacityStroke: "#FFE4C4"
        }
      }
    }, options)
    let notEnoughOption = false;
    for (let k in this.options) {
      if (this.isEmpty(this.options[k])) {
        notEnoughOption = true;
      }
    };
    !notEnoughOption && this.init();
  }

  chart.prototype.init = function () {
    const _this = this,
      nodes = _this.options.nodes,
      edges = _this.options.edges;
    const zoom = d3.zoom()
      .on("zoom", () => {g.attr("transform", d3.event.transform)});
    const svg = d3.select("#" + this.options.id),
      width = +svg.attr("width"),
      height = +svg.attr("height"),
      margin = { top: 0, bottom: 0, left: 0, right: 0 },
      g = svg.append("g").attr("transform", "translate(" + margin.left + ", " + margin.top + ")");

    let edges2 = [], edges3 = [], edges4 = [];
    
    let state = {
      clicked: false,
      drag: false
    }

    const MIDDLE = "middle", TOP = "top", BOTTOM = "bottom";
    const SELECTED = "selected", UNSELECTED = "unselected";
    const COMPANY = "Company", PERSON = "Person";

    const strokeWidthClick = 5, strokeWidth = 1.5;
    
    _this.dataSet(edges, nodes);

    const simulation = d3.forceSimulation(nodes)
      .force("charge", d3.forceManyBody().strength(-400))
      .force("link", d3.forceLink(edges).distance(200).strength(2))
      .force("center", d3.forceCenter(width / 2, height / 2))
      .force("collide", d3.forceCollide().radius(function (d) {
        return d.r + 50;
      }));

    _this.unique(spliceArray(edges, edges2));
    _this.unique(spliceArray(edges2, edges3));
    _this.unique(spliceArray(edges3, edges4));

    _this.setIndex(edges, edges2);
    _this.setIndex(edges3, edges2);
    _this.setIndex(edges4, edges3);

    _this.replaceEmptyItem(edges);
    _this.replaceEmptyItem(edges2);
    _this.replaceEmptyItem(edges3);
    _this.replaceEmptyItem(edges4);

    _this.distinctIndex(edges, edges2);
    _this.distinctIndex(edges2, edges3);
    _this.distinctIndex(edges3, edges4);

    const defs = svg.append("defs");
    for (let i in edges) {
      appendArrow("middlePathArrow" + i, edges[i].index);
    }
    for (let i in edges2) {
      appendArrow("topPathArrow" + i, edges2[i].index);
    }
    for (let i in edges3) {
      appendArrow("bottomPathArrow" + i, edges3[i].index);
    }

    // middle path
    const middlePath = initPath(MIDDLE, edges),
      middlePathTextMask = initPathTextMask(MIDDLE, edges),
      middlePathText = initPathText(MIDDLE, edges),
      // top path
      topPathRect = initPathRect(TOP),
      topPath = initPath(TOP, edges2),
      topPathTextMask = initPathTextMask(TOP, edges2),
      topPathText = initPathText(TOP, edges2),
      //bottom path
      bottomPathRect = initPathRect(BOTTOM),
      bottomPath = initPath(BOTTOM, edges3),
      bottomPathTextMask = initPathTextMask(BOTTOM, edges3),
      bottomPathText = initPathText(BOTTOM, edges3);

    //nodes g
    const gs = g.selectAll(".circle")
      .data(nodes)
      .enter()
      .append("g")
      .attr("class", "gNodes")
      .attr("transform", "translate(-9999, -9999)")
      .call(d3.drag()
        .on("start", dragstart)
        .on("drag", dragged)
        .on("end", dragend)
      );

    d3.selection.prototype.moveToFront = function() {  
      return this.each(function(){
        this.parentNode.appendChild(this);
      });
    };
    d3.selection.prototype.moveToBack = function() {  
      return this.each(function() { 
        var firstChild = this.parentNode.firstChild; 
        if (firstChild) { 
          this.parentNode.insertBefore(this, firstChild); 
        } 
      });
    };

    gs.on("click", function(d) {
      d3.selectAll("circle").attr("stroke-width", o => {
        return o !== d ? `${strokeWidth}px` : `${strokeWidthClick}px`;
      })
      .attr("r", d => d.r - strokeWidth)
      if(state.clicked) {
        if(!d.selectedNode) {
          //这是点击没有关联的node
          // unSelected(d, true, this)
          // state.clicked = false;
        } else {
          //这是点击关联node
          Link(d);
          selected(d, true, this);
        }
      } else {
        //这是第一次点击
        selected(d, true, this);
        state.clicked = true;
      }
      $(document).click(e => {
        //这是点击非nodes
        if(e.target.getAttribute("class") !== "selected"){  
          d3.selectAll("circle").attr("stroke-width", `${strokeWidth}px`)
          unSelected(d, true, this);
          unLink(d);
          state.clicked = false;
        }
      });
    })

    //nodes circle
    const nodesCircle = gs.append("circle")
      .attr("r", d => d.r - strokeWidth)
      .attr("id", d => `circle-${d.index}`)
      .attr("fill", d => d.color)
      .attr("stroke", d => d.stroke)
      .style("cursor", "pointer");

    //nodes text
    const text = gs.append("text")
      .attr("id", "text")
      .style("fill", "#fff")
      .style("cursor", "pointer");

    /**
     * 根据类型决定node文字的属性
     * @param {object} d 
     */
    function sType(d) {
      const offset = 5;
      const type = {
        Company: {
          x: -(d.r / 2 + offset),
          y: -d.r / 2 + offset,
          text: d.properties.name.substring(0, 4)
        },
        Person: {
          x: -(d.r / 2 + offset),
          y: offset,
          text: d.properties.name
        }
      }
      return type;
    }

    text.append("tspan")
      .attr("x", d => {
        return sType(d)[d.labels[0]].x;
      })
      .attr("y", d => {
        return sType(d)[d.labels[0]].y;
      })
      .text(d => {
        if (!sType(d)[d.labels[0]]) return;
        return sType(d)[d.labels[0]].text;
      });

    text.append("tspan")
      .attr("x", (d) => {
        if (d.labels[0] === COMPANY) {
          return sType(d)[d.labels[0]].x - 8;
        }
      })
      .attr("y", (d) => {
        if (d.labels[0] === COMPANY) {
          return sType(d)[d.labels[0]].y + 18;
        }
      })
      .text((d) => {
        return d.properties.name.substring(4, 9);
      });

    text.append("tspan")
      .attr("x", (d) => {
        if (d.labels[0] === COMPANY) {
          return sType(d)[d.labels[0]].x;
        }
      })
      .attr("y", (d) => {
        if (d.labels[0] === COMPANY) {
          return sType(d)[d.labels[0]].y + 36;
        }
      })
      .text((d) => {
        if (d.properties.name.length > 13) {
          return d.properties.name.substring(9, 11) + "...";
        } else {
          return d.properties.name.substring(9, d.properties.name.length);
        }
      });

    function appendArrow(id, _class) {
      defs.append("marker")
        .attr("class", "arrow")
        .attr("markerUnits", "strokeWidth")
        .attr("markerWidth", 10)
        .attr("markerHeight", 10)
        .attr("refY", 3)
        .attr("id", `arrow${_class}`)
        .attr("fill-opacity", .5)
        .append("path")
        .attr("d", "M0,1 L0,5 L5,3 z");
    }

    function ticked() {
      gs.attr("transform", d => {
        let cirX = d.x;
        let cirY = d.y;
        return "translate(" + cirX + ", " + cirY + ")";
      });
      updatePath(middlePath, "middle");
      updateText(middlePathText, "middle");
      updateText(middlePathTextMask, "middle");

      updateRect(topPathRect);
      updatePath(topPath, "top");
      updateText(topPathText, "top");
      updateText(topPathTextMask, "top");

      updateRect(bottomPathRect);
      updatePath(bottomPath, "bottom");
      updateText(bottomPathText, "bottom");
      updateText(bottomPathTextMask, "bottom");
    }

    const scale = d3.scaleLinear();
    scale.domain([0, 200])
      .range([1500, 5000])
    setTimeout(() => {
      $('body')
        .removeClass('loading')
        .addClass('loaded');
      ticked();
      simulation.stop();
      svg.call(zoom);
    }, scale(nodes.length));
    // simulation.on("tick", ticked);

    function initPathRect(direction) {
      let offset = direction === "top" ? 0 : _this.options.spacing;
      let pathLength = document.querySelector(".path-middle").getBBox().width;
      const rect = g.selectAll(".path-rect")
        .data(edges)
        .enter()
        .append("rect")
        .attr("fill", "none")
        .attr("width", document.querySelector(".path-middle").getBBox().width)
        .attr("height", _this.options.spacing);
      return rect;
    }

    function topPathAttrD(d, i) {
      //x偏移
      const x = trigonoFun(d.x, _this.options.spacing, true).x;
      //y偏移
      const y = trigonoFun(d.x, _this.options.spacing, true).y;
      return `M${d.target.x - x} ${d.target.y + y}L${d.source.x - x} ${d.source.y + y}Z`;
    }

    function bottomPathAttrD(d, i) {
      //x偏移
      const x = trigonoFun(d.x, _this.options.spacing, true).x;
      //y偏移
      const y = trigonoFun(d.x, _this.options.spacing, true).y;
      return `M${d.target.x + x} ${d.target.y - y}L${d.source.x + x} ${d.source.y - y}Z`;
    }

    function middlePathAttrD(d, i) {
      let x1 = d.source.x;
      let y1 = d.source.y;
      let x2 = d.target.x;
      let y2 = d.target.y;
      //斜率
      let x = Math.atan2(y2 - y1, x2 - x1);
      x = 180 * x / Math.PI;
      d["x"] = x;

      edges2.map(function (w, j) {
        if (d.index === w.originalIndex) {
          //set x to the other edge
          w["x"] = d.x;
        }
      });
      edges3.map(function (w, j) {
        if (d.index === w.originalIndex) {
          //set x to the other edge
          w["x"] = d.x;
        }
      });
      return `M${x1} ${y1}L${x2} ${y2}`;
    }

    function appendText(direction, arr, isMask) {
      let selection = direction + "-path-text";
      let text = g.selectAll(selection)
        .data(arr)
        .enter()
        .append("text")
        .attr("id", (d, i) => {
          return `${direction}Text${i}`;
        })
        .attr("dy", 3);
      isMask
        ? text.attr("class", `path-${direction}-mask-text`)
        : text.attr("class", `path-${direction}-text`);
      return text;
    }

    function appendTextPath(direction, mask) {
      let idSelection = "#" + direction + "Path";
      mask.append("textPath")
        .attr("xlink:href", (d, i) => {
          return `#d${d.index}`;
        })
        .attr("id", "text")
        .text((d, i) => {
          let type = {
            EMPLOY: d.properties.role || "任职",
            LEGAL: "法人",
            INVEST: "参股"
          }
          return type[d.type];
        });
      return mask;
    }

    function initPathTextMask(direction, arr) {
      const mask = appendText(direction, arr, true);
      appendTextPath(direction, mask)
        .style("stroke", "white")
        .style("stroke-width", "0.15em");
      return mask;
    }

    function initPathText(direction, arr) {
      const text = appendText(direction, arr);
      appendTextPath(direction, text);
      return text;
    }

    function initPath(direction, arr) {
      const attrClass = "path-" + direction,
        attrId = direction + "Path",
        selection = "." + attrId,
        links = g.selectAll(selection)
          .data(arr)
          .enter()
          .append("path")
          .attr("class", attrClass)
          .attr("id", d => {
            return `d${d.index}`;
          });
      return links;
    }

    /**
     * 三角形根据侧边求其他两边
     * @param {number} x //侧边长度
     * @param {number} a //角度
     * @param {boolean} isComplementaryAngle //是否余角
     */
    function trigonoFun(x, a, isComplementaryAngle) {
      const o = {
        x: 0,
        y: 0
      };
      let alpha = isComplementaryAngle ? 90 - x : x;
      alpha = alpha * Math.PI / 180;
      //偏移
      o.y = Math.sin(alpha) * a;
      o.x = Math.cos(alpha) * a;
      return o;
    }

    function Link(d) {
      linkActive(middlePath, d);
      linkActive(topPath, d);
      linkActive(bottomPath, d);
    }

    gs.on("mouseover", function(d) {
      if(state.clicked || state.drag) return;
      Link(d);
      let msg = d.properties.name;
      if($(this).find("title")[0]) return;
      const title = document.createElementNS("http://www.w3.org/2000/svg","title"),
      text = document.createTextNode(msg);
      title.appendChild(text);
      this.appendChild(title);
    });

    function linkActive(link, d) {
      link.style("stroke-opacity", o => {
        if(o.source === d || o.target === d) {
          d3.select(`#arrow${o.index}`).attr("fill-opacity", 1);
          return 1;
        }
      })
    }

    function unLinkActive(link, d) {
      link.style("stroke-opacity", o => {
        if(o.source === d || o.target === d) {
          d3.select(`#arrow${o.index}`).attr("fill-opacity", .5);
          return .3;
        }
      })
    }

    gs.on("mouseout", d => {
      if(state.clicked || state.drag) return;
      unLink(d);
    });

    /**
     * 取消线的hover高亮
     * @param {Object} d 
     */
    function unLink(d) {
      unLinkActive(middlePath, d);
      unLinkActive(topPath, d);
      unLinkActive(bottomPath, d);
    }

    /**
     * link置顶
     * @param {Object} d 
     */
    function stickyLink(d, nodes) {
      selectFilter(".path-middle", d).raise();
      selectFilter(".path-middle-mask-text", d).raise();
      selectFilter(".path-middle-text", d).raise();
      selectFilter(".path-top", d).raise();
      selectFilter(".path-top-mask-text", d).raise();
      selectFilter(".path-top-text", d).raise();
      selectFilter(".path-bottom", d).raise();
      selectFilter(".path-bottom-mask-text", d).raise();
      selectFilter(".path-bottom-text", d).raise();
    }

    /**
     * node置顶
     * @param {Object} d 
     */
    function stickNode(d) {
      nodesCircle.style("stroke-opacity", function (o) {
        if (isConnect(d, o)) {
          d3.select($(`#circle-${o.index}`).parent("g")[0]).raise();
        }
      })
    }
    
    const linkIndex = {};
    edges.map(d => {
      linkIndex[`${d.source.index},${d.target.index}`] = 1;
    });

    /**
     * 两个node是否关联
     * @param {Object} a node1 d
     * @param {Object} b node2 d
     */
    function isConnect(a, b) {
      return linkIndex[`${a.index},${b.index}`] || linkIndex[`${b.index},${a.index}`] || a.index === b.index;
    }

    /**
     * link交互相关
     * @param {number} opacity 透明度
     * @param {Object} d 数据集
     * @param {Object} link d3 link obj
     * @param {String} direction 方向
     * @param {String} state start or end
     */
    function fadePath(opacity, d, link, direction, state) {
      link.style("stroke-opacity", function(o, i) {
        if(o.source === d || o.target === d) {
          //关联的link
          return 1;
        } else {
          //不关联的link
          const arrowOpacity = state === "end" ? .5 : opacity;
          d3.select(`#arrow${o.index}`).attr("fill-opacity", arrowOpacity);
          return opacity;
        }
      })
    }
    
    function fadePathText(opacity, d, text, mask) {
      text.attr("fill-opacity", o =>
        (o.source === d || o.target === d ? 1 : opacity));
      mask.attr("opacity", o =>
        (o.source === d || o.target === d ? 1 : opacity));
    }

    function fadeNodeText(opacity, d, text) {
      text.style("fill-opacity", function(o) {
        const thisOpacity = isConnect(d, o) ? 1 : opacity;
        return thisOpacity;
      })
    }

    /**
     * 关联node的link过滤器(两边)
     * @param {String} doc d3 selection
     * @param {Object} d 
     */
    function selectFilter(doc, d) {
      return d3.selectAll(doc)
        .filter(o => (o.source.index === d.index || o.target.index === d.index));
    }

    /**
     * 关联node的link过滤器(一边)
     * @param {String} doc d3 selection
     * @param {Object} d 
     */
    function selectFilterNot(doc, d) {
      return d3.selectAll(doc)
        .filter(o => (o.source.index !== d.index || o.target.index !== d.index));
    }

    /**
     * 给高亮nodes和其他nodes添加类名
     * @param {String} _class 类名
     * @param {Object} _this node doc
     */
    function toggleSelected(_class, _this) {
      _this.setAttribute("class", _class);
      const text = $(_this).next("text")[0];
      text.setAttribute("class", _class);
      const tspan = $(text).find("tspan");
      tspan.each((i, v) => {
        v.setAttribute("class", _class);
      });
    }

    /**
     * 选中node及其关联node设置颜色
     * @param {Object} o 数据集
     * @param {Object} _this2 node doc
     */
    function selectedNodeSetColor(o, _this2) {
      if(o.properties.name === "易关通(广州)网络科技有限公司") {
          _this2.setAttribute("fill", _this.options.color.main.node);
          _this2.setAttribute("stroke", _this.options.color.main.stroke);
      } else {
        if (o.color === _this.options.color.node.Company) {
          _this2.setAttribute("fill", _this.options.color.node.Company);
          _this2.setAttribute("stroke", _this.options.color.stroke.Company);
        } else {
          _this2.setAttribute("fill", _this.options.color.node.Person);
          _this2.setAttribute("stroke", _this.options.color.stroke.Person);
        }
      }
     
    }

    function findCircle(o) {
      return $(`#circle-${o.index}`);
    }

    /**
     * 切换鼠标手势
     * @param {Object} o 数据集
     * @param {String} cursor 鼠标手势
     */
    function toggleCursor(o, cursor) {
      let circle = findCircle(o);
      let text = circle.next("#text");
      d3.select(circle[0]).style("cursor", cursor);
      d3.select(text[0]).style("cursor", cursor);
    }

    /**
     * 高亮显示关联的node，其他透明化
     * @param {Object} d 数据集
     * @param {Boolean} clicked 是否是点击事件
     * @param {Object} _this2 点击的document
     */
    function selected(d, clicked, _this2) {
      nodesCircle.style("stroke-opacity", function (o) {
      if (!isConnect(d, o)) {
        //没有关联的nodes
        if(o.properties.name === _this.options.dominant) {
          this.setAttribute("fill", _this.options.color.main.opacityNode);
          this.setAttribute("stroke", _this.options.color.main.opacityStroke);
        } else {
        o.color === _this.options.color.node.Company
          ?this.setAttribute("fill", _this.options.color.opacityNode.Company)
          :this.setAttribute("fill", _this.options.color.opacityNode.Person);
        o.stroke === _this.options.color.stroke.Company
          ? this.setAttribute("stroke", _this.options.color.opacityStroke.Company)
          : this.setAttribute("stroke", _this.options.color.opacityStroke.Person);
        }
        o["selectedNode"] = false;
        toggleSelected("unSelected", this);
        toggleCursor(o, "default");
      } else {
        //关联的nodes
        o["selectedNode"] = true;
        selectedNodeSetColor(o, this);
        toggleSelected("selected", this);
        toggleCursor(o, "pointer");
      }
    });
      fadePath(.1, d, middlePath, "middle", "start");
      fadePath(.1, d, topPath, "top", "start");
      fadePath(.1, d, bottomPath, "top", "start");
      fadePathText(0, d, middlePathText, middlePathTextMask, true);
      fadePathText(0, d, topPathText, topPathTextMask, true);
      fadePathText(0, d, bottomPathText, bottomPathTextMask, true);
      fadeNodeText(0, d, text);
    }

    function dragstart(d) {
      state.drag = true;
      if(state.clicked) return;
      selected(d, false, this);
    }

    function dragend(d) {
      d3.selectAll(".gNodes").sort((a, b) => {
        if(a.index === d.index) return 1;
        else return -1;
      });
      state.drag = false;
      if(state.clicked) return;
      unSelected(d, false, this);
    }

    /**
     * 取消选中高亮
     * @param {Object} d 数据集
     * @param {Boolean} clicked 是否点击事件
     * @param {Object} _this2 点击或者拖拽的document
     */
    function unSelected(d, clicked, _this2) {
      d3.select(_this2).attr("stroke-width", `${strokeWidth}px`);
      nodesCircle.style("stroke-opacity", function (o) {
        toggleCursor(o, "pointer");
        selectedNodeSetColor(o, this);
      })
      fadeNodeText(1, d, text);
      fadePath(.3, d, middlePath, MIDDLE, "end");
      fadePath(.3, d, topPath, TOP, "end");
      fadePath(.3, d, bottomPath, TOP, "end");
      fadePathText(1, d, middlePathText, middlePathTextMask);
      fadePathText(1, d, topPathText, topPathTextMask);
      fadePathText(1, d, bottomPathText, bottomPathTextMask);
    }

    /**
     * 置顶操作
     * @param {String} _class 类名
     * @param {Object} d 
     */
    function sticky(_class, d) {
      d3.selectAll(_class).filter(o => (o.source.index !== d.index || o.target.index !== d.index)).lower()
    }

    function dragged(d) {
      if (state.clicked) {
        if(findCircle(d).attr("class") === "unSelected") return;
      }
      $(".unSelected").parent("g").each((i, v) => {
        d3.select(v).lower();
      })

      sticky(".path-middle", d);
      sticky(".path-top", d);
      sticky(".path-bottom", d);
      
      d.x = d3.event.x;
      d.y = d3.event.y;
      const node = d3.select(this);
      node.attr("transform", function (v, i) {
        return "translate(" + d.x + " ," + d.y + ")";
      });

      updatePath(selectFilter(".path-middle", d), MIDDLE, true);
      updateText(selectFilter(".path-middle-text", d), MIDDLE);
      updateText(selectFilter(".path-middle-mask-text", d), MIDDLE);

      updateRect(selectFilter(".path-top-rect", d), TOP);
      updatePath(selectFilter(".path-top", d), TOP, true);
      updateText(selectFilter(".path-top-text", d), TOP);
      updateText(selectFilter(".path-top-mask-text", d), TOP);

      updateRect(selectFilter(".path-bottom-rect", d), BOTTOM);
      updatePath(selectFilter(".path-bottom", d), BOTTOM, true);
      updateText(selectFilter(".path-bottom-text", d), BOTTOM);
      updateText(selectFilter(".path-bottom-mask-text"  , d), BOTTOM);
    }

    function textFixedX(direction) {
      return (d, i) => {
        const pathDom = $(`#d${d.index}`)[0];
        const pathWidth = pathDom.getTotalLength();
        const scale = direction === "middle" ? 2 : 4;
        let offset = 0;
        const role = d.properties.role;
        if (role) offset = role.length > 6 ? 26 : 0;
        let x = pathWidth / scale - d.source.r / 2 - offset;
        return x;
      }
    }

    function textFixedTransform(direction) {
      return (d, i) => {
        const svgDom = $(`#d${d.index}`)[0].getBBox();
        const x = svgDom.x;
        const y = svgDom.y;
        const rotate0 = `rotate(0, ${x + svgDom.width / 2}, ${y + svgDom.height / 2})`;
        const rotate180 = `rotate(180, ${x + svgDom.width / 2}, ${y + svgDom.height / 2})`;
        let rotate = -90 < d.x && d.x < 90
          ? (direction === "middle" ? rotate0 : rotate180)
          : (direction === "middle" ? rotate180 : rotate0);
        return rotate;
      }
    }

    function updateText(text, direction) {
      text.attr("x", textFixedX(direction))
        .attr("transform", textFixedTransform(direction));
    }

    function updateRect(rect) {
      rect.attr("x", (d) => {
        return d.source.x;
      })
      .attr("y", (d) => {
        return d.source.y;
      })
      .attr("transform", (d, i) => {
        return "rotate(" + d.x + "," + d.source.x + "," + d.source.y + ")";
      })
    }

    /**
     * 重绘链接线
     * @param {Object} path d3 Selection Object
     * @param {String} direction 上中下哪条线
     * @param {Boolean} isDrag 是否是拖拽事件
     */
    function updatePath(path, direction, isDrag) {
      path.attr("d", (d, i) => {
        if (direction === TOP) {
          return topPathAttrD(d, i);
        } else if (direction === BOTTOM) {
          return bottomPathAttrD(d, i);
        } else if (direction === MIDDLE) {
          return middlePathAttrD(d, i);
        }
      })
        .attr("stroke", (d) => {
          return d.color;
        })
        .attr("stroke-width", 1.5)
        if (direction !== MIDDLE) {
          path.attr("marker-end", (d, i) => {
            const arrow = d3.select(`#arrow${d.index}`);
            arrow.attr("fill", _this.options.color.line[d.type])
              .attr("refX", 26)
              .attr("orient", d.x)
            return `url(#arrow${d.index})`;
          });
        } else {
          path.attr("marker-end", (d, i) => {
            const arrow = d3.select(`#arrow${d.index}`);
            arrow.attr("fill", _this.options.color.line[d.type])
              .attr("refX", 28)
              .attr("orient", "auto")
            return `url(#arrow${d.index})`;
          });
        }
    }
  }

  /**
   * 判断是否为空
   * @param {object} obj
   */
  chart.prototype.isEmpty = function (obj) {
    if (obj == null) return true;
    if (obj.length > 0) return false;
    if (obj.length === 0) return true;
    for (let key in obj) {
      if (hasOwnProperty.call(obj, key)) return false;
    }
    return false;
  }

  /**
   * 数组去重
   * @param {Array} arr 
   */
  chart.prototype.unique = function (arr) {
    const res = new Map();
    return arr.filter(a => !res.has(a) && res.set(a, 1));
  }

  chart.prototype.unrepeat = function (obj) {
    var isArray = obj instanceof Array;
    for (var k in obj) {
      if (k === "undefined") delete obj[k];
      else if (typeof obj[k] == "object") this.unrepeat(obj[k]);
    }
    return obj;
  }

  /**
   * 去除数组empty元素
   * @param {Array} arr 
   */
  chart.prototype.replaceEmptyItem = function (arr) {
    for (let i = 0, len = arr.length; i < len; i++) {
      if (!arr[i] || arr[i] == '') {
        arr.splice(i, 1);
        len--;
        i--;
      }
    }
  }

  /**
   * 去除数组index与另一个数组相同的部分
   * @param {Array} arr1 要去除重复的数组
   * @param {Array} arr2 与之重复的数组
   */
  chart.prototype.distinctIndex = function (arr1, arr2) {
    for (let i in arr1) {
      for (let j in arr2) {
        if (arr1[i].index === arr2[j].index) {
          arr1.splice(i, 1);
        }
      }
    }
  }

  /**
     * 把关系数组的id指向nodes数组
     * @param {Array} d1 关系数组
     * @param {Array} d2 Nodes数组
     */
  chart.prototype.dataSet = function (d1, d2) {
    const _this = this;

    d1.map(v => {
      v["source"] = v.startNode;
      v["target"] = v.endNode;
    })

    d1.some((v, i) => {
      let type = this.options.color.line;
      v["color"] = type[v.type];
      d2.some(function (w, j) {
        const type = {
          Company: function () {
            w["r"] = 35;
            w["stroke"] = _this.options.color.stroke.Company;
            w["color"] = _this.options.color.node.Company;
          },
          Person: function () {
            w["r"] = 25;
            w["stroke"] = _this.options.color.stroke.Person;
            w["color"] = _this.options.color.node.Person;
          }
        };
        new type[w.labels[0]];
        if(w.properties.name === _this.options.dominant) {
          w["color"] = _this.options.color.main.node;
          w["stroke"] = _this.options.color.main.stroke;
        }
        if (v.source === w.id) {
          v.source = w;
        }
        if (v.target === w.id) {
          v.target = w;
        }
      });
    });
  }

  /**
   * 
   * @param {Array} d1 
   * @param {Array} d2 
   */
  chart.prototype.setIndex = function (d1, d2) {
    d1.map(function (v, i) {
      d2.map(function (w, j) {
        if (v.originalIndex === j) {
          v.originalIndex = w.originalIndex;
        }
      });
    });
  }


  /**
   * 把一对一的多重关系分离出新的数组
   * @param {Array} arr1 要从中取数据的数组，不改变原数组
   * @param {Array} arr2 要把数据放到的新数组
   */
  function spliceArray(arr1, arr2) {
    let arr = [];//存放原数组切割出去的元素的index
    for (let i = 0; i < arr1.length - 1; i++) {
      for (let j = i + 1; j < arr1.length; j++) {
        if (arr1[j] && arr1[i]) {
          if (arr1[i].source === arr1[j].source && arr1[i].target === arr1[j].target) {
            arr2[j] = arr1[j];
            arr2[j]["originalIndex"] = arr1[i].index;
            arr.push(j);
            arr1[j]["isRedundancy"] = true;
          }
        }
      }
    }
    return arr;
  }

  window.chart = chart;
}());

